#include "Grid.h"
#include "base\Engine.h"
#include "utils\Log.h"
#include "game\World.h"
#include "Constants.h"

const int SQUARE_SIZE = 42;
const int XM[4] = {SQUARE_SIZE,-SQUARE_SIZE,0,0};
const int YM[4] = {0,0,SQUARE_SIZE,-SQUARE_SIZE};
const int LD[4] = {1,0,3,2};

Grid::Grid() {
	gEngine->addMouseController(this);
	// create the dynamic sprite node for the pieces
	m_SpriteNode = new ds::DynamicSpriteNode("Cubes","TextureArrayMaterial");
	gEngine->getWorld().attachNode(m_SpriteNode,"Cubes");	
	setActive(false);
	m_AllPieces.create(MAX_X,MAX_Y);	
}

// -------------------------------------------------------------
//
// -------------------------------------------------------------
Grid::~Grid() {
	clear();
}

// -------------------------------------------------------------
//
// -------------------------------------------------------------
void Grid::clear() {
	Pieces::iterator pit = m_Pieces.begin();
	while ( pit != m_Pieces.end() ) {
		delete pit->sprite;
		pit = m_Pieces.erase(pit);
	}	
	ShrinkList::iterator it = m_ShrinkList.begin();
	while ( it != m_ShrinkList.end() ) {
		delete it->sprite;
		it = m_ShrinkList.erase(it);
	}	
}

// -------------------------------------------------------------
//
// -------------------------------------------------------------
void Grid::setActive(bool active) {
	running = active;	
}

// -------------------------------------------------------------
//
// -------------------------------------------------------------
void Grid::create(int level) {
	int xp = STARTX;	
	clear();
	for ( int x = 0; x < MAX_X; ++x ) {
		int yp = STARTY;
		for ( int y = 0; y < MAX_Y; ++y ) {
			Piece p;			
			p.color = gEngine->random(0,level);
			int offset = p.color*SQUARE_SIZE;	
			p.sprite = new ds::Sprite(ds::Vec2(xp,yp-600),ds::Rect(750,offset,SQUARE_SIZE,SQUARE_SIZE));	
			p.position = ds::Vec2(xp,yp);
			p.fallingVelocity = 250 + y * 10;
			m_Pieces.push_back(p);
			m_AllPieces.set(x,y,p);
			yp += SQUARE_SIZE;
		}
		xp += SQUARE_SIZE;
	}
	ShrinkList::iterator it = m_ShrinkList.begin();
	while ( it != m_ShrinkList.end() ) {
		delete it->sprite;
		it = m_ShrinkList.erase(it);
	}
	clickCounter = 0.5f;
	points = 0;
	movesLeft = 1000;	
	FIRE_EMPTY_EVENT(EVN_RESET_GAME);
	m_State = GS_FALLING;
	//m_Checkboard->setActive(true);
	running = true;
}

// -------------------------------------------------------------
//
// -------------------------------------------------------------
void Grid::OnButtonDown(int button) {
	if ( running && movesLeft > 0 && m_State == GS_NORMAL ) {
		if ( button == 0 ) {
			int mx = gEngine->getMousePosX();
			int my = gEngine->getMousePosY();
			int xo = (mx-STARTX-15)/SQUARE_SIZE;
			int yo = (my-STARTY-15)/SQUARE_SIZE;
			removePiece(xo,yo);
			clickCounter = 0.0f;
		}	
	}
}

// -------------------------------------------------------------
//
// -------------------------------------------------------------
bool Grid::update(const float& elapsedTime) {			
	if ( m_State == GS_FLOATING ) {
		ShrinkList::iterator it = m_ShrinkList.begin();
		bool done = false;
		while ( it != m_ShrinkList.end() ) {
			it->sprite->scale -= ds::Vec2(0.05f,0.05f);
			it->sprite->position = it->position + ( 1.0f - it->sprite->scale.x) * SQUARE_SIZE * 0.5f;
			if ( it->sprite->scale.x < 0.1f ) {
				delete it->sprite;
				it = m_ShrinkList.erase(it);
				done = true;
			}
			else {
				++it;
			}
		}		
		if ( done ) {			
			m_State = GS_NORMAL;
			moveDown();
		}
	}
	else if ( m_State == GS_FALLING ) {
		int cnt = 0;
		for ( size_t i = 0; i < m_Pieces.size(); ++i ) {
			Piece* p = &m_Pieces[i];
			ds::Vec2 pp = p->sprite->position;
			if ( pp.y < p->position.y ) {
				p->sprite->position.y += p->fallingVelocity * elapsedTime;
			}
			else {
				p->sprite->position = p->position;
				++cnt;
			}
		}
		if ( cnt == m_Pieces.size() ) {
			FIRE_EMPTY_EVENT(EVN_LEVEL_READY);
			m_State = GS_NORMAL;
		}
	}
	for ( size_t i = 0; i < m_ShrinkList.size(); ++i ) {
		m_SpriteNode->addSprite(m_ShrinkList[i].sprite);
	}
	for ( size_t i = 0; i < m_Pieces.size(); ++i ) {
		m_SpriteNode->addSprite(m_Pieces[i].sprite);
	}
	return true;
}

// -------------------------------------------------------------
//
// -------------------------------------------------------------
void Grid::removePiece(int x,int y) {
	int xp = x * SQUARE_SIZE + STARTX;
	int yp = y * SQUARE_SIZE + STARTY;
	int p = checkSurrounding(xp,yp);
	if ( p > 0 ) {				
		FIRE_EVENT(EVN_ADD_SCORE,&p,sizeof(int));
		FIRE_EMPTY_EVENT(EVN_START_FADING);
		movesLeft = getMovesLeft();
		if ( movesLeft == 0 ) {	
			m_State = GS_IDLE;			
			FIRE_EMPTY_EVENT(EVN_GAME_OVER);
			running = false;			
		}
	}
	else {
		gEngine->getAudioManager().play("UI_Misc21",100,false);
	}
}

// -------------------------------------------------------------
//
// -------------------------------------------------------------
int Grid::getColor(int xp,int yp) {
	Piece& p = m_AllPieces.get(xp,yp);
	if ( p.sprite != 0 ) {
		return p.color;
	}
	/*
	for ( size_t i = 0; i < m_Pieces.size(); ++i ) {
		Piece* p = &m_Pieces[i];
		if ( p->position.x == xp && p->position.y == yp ) {
			return p->color;
		}
	}
	*/
	return -1;
}

// -------------------------------------------------------------
//
// -------------------------------------------------------------
void Grid::moveDown() {
	Pieces::reverse_iterator rit;
	for ( rit = m_Pieces.rbegin() ; rit < m_Pieces.rend(); ++rit ) {
		Piece p = *rit;
		int sy = p.position.y + SQUARE_SIZE;
		int next = getIndex(p.position.x,sy);
		while ( next == -1 && sy < (STARTY + MAX_Y * SQUARE_SIZE) ) {
			sy += SQUARE_SIZE;
			p.position.y += SQUARE_SIZE;
			next = getIndex(p.position.x,sy);
		}
		p.sprite->position = p.position;
		(*rit).position = p.position;
	}
	// check if we have empty rows and if yes shift the 
	// rest to the left
	int xp = STARTX + MAX_X * SQUARE_SIZE - SQUARE_SIZE;
	for ( int i = 0 ; i < MAX_X;++i ) {
		if ( isRowEmpty(xp) ) {
			shiftLeft(xp);
		}
		xp -= SQUARE_SIZE;
	}
}

// -------------------------------------------------------------
//
// -------------------------------------------------------------
void Grid::shiftLeft(int xp) {
	for ( size_t i = 0; i < m_Pieces.size(); ++i ) {
		Piece* p = &m_Pieces[i];
		if ( p->position.x > xp ) {
			ds::Vec2 pos = p->position;
			pos.x -= SQUARE_SIZE;
			p->sprite->position = ds::Vec2(pos.x,pos.y);
			p->position = ds::Vec2(pos.x,pos.y);
		}
	}
}

// -------------------------------------------------------------
//
// -------------------------------------------------------------
bool Grid::isRowEmpty(int xp) {
	int yp = STARTY;
	int ret = 0;
	bool empty = true;
	for ( int i = 0; i < MAX_Y;++i) {
		ret = getIndex(xp,yp);
		if ( ret != -1 ) {
			empty = false;
		}
		yp += SQUARE_SIZE;
	}	
	return empty;
}

// -----------------------------------------------------
// Returns the matching index of the Pieces vector for
// the given coordinates
// [in] xp - x position
// [in] yp - y position
// [out] the index or -1 of there was no match
// -----------------------------------------------------
int Grid::getIndex(int xp,int yp) {
	if ( xp > (STARTX + MAX_X*SQUARE_SIZE) || yp > ( STARTY + MAX_Y*SQUARE_SIZE) ) {
		return -1;
	}
	for ( size_t i = 0; i < m_Pieces.size(); ++i ) {
		Piece* p = &m_Pieces[i];
		if (p->position.x == xp && p->position.y == yp ) {
			return i;
		}
	}			
	return -1;
}

// -----------------------------------------------------
// Checks either there are pieces with the same
// color around this one
// [in] xp - x position of the particular piece
// [in] yp - y position
// [out] returns the number of matching pieces
// -----------------------------------------------------
int Grid::checkSurrounding(int xp,int yp) {
	std::vector<ds::Vec2> colors;
	check(xp,yp,-1,&colors,true);
	if ( colors.size() > 0 ) {
		int idx = getIndex(xp,yp);
		if ( idx != -1 ) {
			m_ShrinkList.push_back(m_Pieces[idx]);
			m_Pieces.erase(m_Pieces.begin()+idx);	
		}
		for ( size_t i = 0; i < colors.size(); ++i ) {
			int idx = getIndex(colors[i].x,colors[i].y);
			if ( idx != -1 ) {
				m_ShrinkList.push_back(m_Pieces[idx]);
				m_Pieces.erase(m_Pieces.begin()+idx);	
			}
		}		
		m_State = GS_FLOATING;
		gEngine->getAudioManager().play("Whoosh",100,false);
		return colors.size()+1;
	}
	return 0;	
}

// -------------------------------------------------------------
//
// -------------------------------------------------------------
void Grid::check(int xp,int yp,int lastDir,std::vector<ds::Vec2> *list,bool rec) {
	int idx = getIndex(xp,yp);
	if ( idx != -1 ) {
		Piece p = m_Pieces[idx];
		int color = p.color;
		for ( int i = 0 ; i < 4; ++i ) {
			if ( i != lastDir ) {
				int sx = xp + XM[i];
				int sy = yp + YM[i];
				int next = getIndex(sx,sy);
				if ( next != -1 ) {
					Piece np = m_Pieces[next];
					int nc = np.color;
					while ( color == nc ) {												
						bool recheck = true;
						for ( size_t r = 0; r < list->size(); ++r ) {
							ds::Vec2 v = list->at(r);
							if ( v.x == np.position.x && v.y == np.position.y ) {
								recheck = false;
							}
						}
						list->push_back(np.position);
						if ( recheck && rec ) {
							check(np.position.x,np.position.y,LD[i],list,rec);
						}						
						sx += XM[i];
						sy += YM[i];
						next = getIndex(sx,sy);
						if ( next != -1 ) {
							np = m_Pieces[next];
							nc = np.color;
						}
						else {
							nc = -1;
						}
					}
				}
			}
		}
	}
}

// -----------------------------------------------------
// Returns the number of moves that are left
// [out] number of pieces that have at least one 
//       matching neighbor
// -----------------------------------------------------
int Grid::getMovesLeft() {
	int cnt = 0;
	std::vector<ds::Vec2> colors;
	for ( size_t i = 0; i < m_Pieces.size(); ++i ) {
		colors.clear();
		check(m_Pieces[i].position.x,m_Pieces[i].position.y,-1,&colors,false);
		if ( colors.size() > 0 ) {
			++cnt;
		}
	}
	return cnt;
}
